package com.wlinker.driver.opc.ua;

import cn.hutool.core.bean.BeanUtil;
import cn.wlinker.driver.common.AbstractDriver;
import cn.wlinker.driver.common.utils.DataTypeEnumSupplier;
import cn.wlinker.driver.common.utils.IConnectHelper;
import cn.wlinker.driver.common.utils.IDataTypeEnum;
import cn.wlinker.driver.common.exception.IncompleteInfoException;
import com.wlinker.driver.opc.ua.constant.UnsignedDataTypeEnum;
import com.wlinker.driver.opc.ua.domain.OpcUaBean;
import com.wlinker.driver.opc.ua.domain.OpcUaConnectBean;
import com.wlinker.driver.opc.ua.domain.OpcUaRes;
import com.wlinker.driver.opc.ua.domain.OpcUaSimpleRes;
import com.wlinker.driver.opc.ua.utils.OpcUaConnectHelper;
import lombok.SneakyThrows;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.model.nodes.variables.BaseVariableTypeNode;
import org.eclipse.milo.opcua.sdk.client.nodes.UaNode;
import org.eclipse.milo.opcua.stack.core.types.builtin.*;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UShort;

import java.util.*;

/**
 * OpcUa驱动
 *
 * @author gxsjx
 * @version 1.0
 * @date 2023/7/13
 * Copyright © wlinker.cn
 */
public class OpcUaDriver extends AbstractDriver<OpcUaConnectBean, OpcUaBean, OpcUaSimpleRes> {

    /**
     * 注册数据类型
     */
    @Override
    protected void afterCreated() {
        DataTypeEnumSupplier.register(UnsignedDataTypeEnum.values());
    }

    public OpcUaDriver(Map<String, Object> connectionMap, IConnectHelper connectHelper) {
        super(connectionMap, connectHelper);
    }

    public OpcUaClient getConnection() throws Exception {
        return getConnectHelper().getConnection(getConnectionBean());
    }

    @Override
    public OpcUaConnectBean getConnectionBean() {
        return BeanUtil.toBean(connectionMap, OpcUaConnectBean.class);
    }

    @Override
    public OpcUaConnectHelper getConnectHelper() {
        return (OpcUaConnectHelper) this.connectHelper;
    }

    @Override
    @SneakyThrows
    public List<OpcUaRes> discovery() {
        OpcUaClient connection = getConnection();
        Set<UaNode> uaNodes = getConnectHelper().listNode(connection, null);
        List<OpcUaRes> opcUaResList = new LinkedList<>();
        for (UaNode uaNode : uaNodes) {
            OpcUaRes opcUaRes = new OpcUaRes();
            UShort namespaceIndex = uaNode.getBrowseName().getNamespaceIndex();
            opcUaRes.setNamespace(namespaceIndex.intValue());
            Object identifier = uaNode.getNodeId().getIdentifier();
            opcUaRes.setIdentifier(String.valueOf(identifier));
            String displayName = uaNode.getDisplayName().getText();
            opcUaRes.setName(displayName);
            DataValue dataValue = ((BaseVariableTypeNode) uaNode).getValue();
            Object value = dataValue.getValue().getValue();
            opcUaRes.setValue(value);
            StatusCode statusCode = dataValue.getStatusCode();
            if (statusCode != null) {
                opcUaRes.setIsGood(statusCode.isGood());
            }
            DateTime sourceTime = dataValue.getSourceTime();
            String dataType = IDataTypeEnum.getDataType(value);
            opcUaRes.setDataType(dataType);
            if (Objects.nonNull(sourceTime)) {
                opcUaRes.setSourceTime(sourceTime.getJavaDate());
            }
            opcUaResList.add(opcUaRes);
        }
        return opcUaResList;
    }

    @SneakyThrows
    @Override
    public OpcUaSimpleRes read(OpcUaBean dto) {
        Integer namespace = getNameSpace(dto);
        OpcUaClient connection = getConnection();
        DataValue dataValue = getConnectHelper().readNodeValue(connection, namespace, dto.getIdentifier());
        OpcUaSimpleRes opcUaRes = new OpcUaSimpleRes();
        opcUaRes.setValue(dataValue.getValue().getValue());
        opcUaRes.setIdentifier(dto.getIdentifier());
        StatusCode statusCode = dataValue.getStatusCode();
        if (statusCode != null) {
            opcUaRes.setIsGood(statusCode.isGood());
        }
        return opcUaRes;
    }

    @SneakyThrows
    @Override
    public Boolean write(OpcUaBean dto) {
        //数据类型不能为空
        String dataType = dto.getDataType();
        if (Objects.isNull(dataType)) {
            throw new IncompleteInfoException("数据类型dataType不能为空");
        }
        Integer namespace = getNameSpace(dto);
        Object targetValue = IDataTypeEnum.toTargetValue(dataType, dto.getValue());
        OpcUaClient connection = getConnection();
        return getConnectHelper().writeNodeValue(connection, namespace, dto.getIdentifier(), targetValue);
    }

    /**
     * 获取命名空间
     * @param dto
     * @return
     */
    private Integer getNameSpace(OpcUaBean dto) {
        Integer namespace = dto.getNamespace();
        // 默认命名空间为2
        if (namespace == null) {
            namespace = 2;
        }
        return namespace;
    }
}
